function toYMDString( date )
{
    return date.getFullYear() + "-" + pad(date.getMonth() + 1, 2, true, '0') + "-" + pad(date.getDate(), 2, true, '0');
}

function toFullDateString( dateTime )
{
    var str = toYMDString(dateTime);
    var offsetMin = dateTime.getTimezoneOffset() % 60;
    var offsetHr = dateTime.getTimezoneOffset() / 60;
    var isPos = offsetMin > 0;
    var offsetS = isPos ? '+' : '-';
    if ( !isPos )
    {
        offsetMin = -offsetMin;
    }
    offsetS += pad(offsetHr, 2, true, '0') + ":" + pad(offsetMin, 2, true, '0');
    str += "T" + pad(dateTime.getHours(), 2, true, '0') + ':' + pad(dateTime.getMinutes(), 2, true, '0') + ':';
    str += pad(dateTime.getSeconds(), 2, true, '0') + offsetS;
    return str;
}

function pad( str, requiredLength, leading, chr )
{
    str = new String(str);
    // insurance
    if ( !leading )
        leading = true;
    if ( !chr )
    {
        chr = ' ';
    }
    while ( str.length < requiredLength )
    {
        if ( leading )
        {
            str = chr + str;
        }
        else
        {
            str = str + chr;
        }
    }
    return str;
}

function tf_debug( ex )
{
    if ( ex && ex.result && ex.result == 2147500037 )
    {
        return;
    }
    var msg = ex;
    if ( ex instanceof Error )
    {
        msg = "An Error has occured in the TestFire Extension: \n\n";
        msg += ex.toString() + "\n";
        msg += "At line: " + ex.lineNumber + "\n";
        msg += "In file: " + ex.fileName + "\n\n";
        msg += "Stack Trace: \n";
        var stack = ex.stack.split("\n");
        msg += stack.length != 0 ? stack[0] + "\n" : "";
        for ( var i = 1; i < stack.length; i++ )
        {
            msg += "    " + stack[i] + "\n";
        }
        msg += ex.stack;
    }
    else
    {
        msg = "An Error has occured in the TestFire Extension: \n\n" + msg;
    }

    alert(msg);
}

function tf_hasProperties( object )
{
    for ( var name in object )
    {
        return true;
    }
    return false;
}
var UTIL = {};
(function()
{

    this.TYPE_BOOLEAN = 1;
    this.TYPE_INTEGER = 2;
    this.TYPE_FLOAT = 4;
    this.TYPE_STRING = 8;

    this.TYPE_NUMBER = this.TYPE_INTEGER | this.TYPE_FLOAT;
    this.TYPE_ANY = this.TYPE_BOOLEAN | this.TYPE_STRING | this.TYPE_INTEGER | this.TYPE_FLOAT;

    this.tf_debug = function( ex )
    {
        if ( ex && ex.result && ex.result == 2147500037 )
        {
            return;
        }
        var msg = ex;
        if ( ex instanceof Error )
        {
            msg = "An Error has occured in the TestFire Extension: \n\n";
            msg += ex.toString() + "\n";
            msg += "At line: " + ex.lineNumber + "\n";
            msg += "In file: " + ex.fileName + "\n\n";
            msg += "Stack Trace: \n";
            var stack = ex.stack.split("\n");
            msg += stack.length != 0 ? stack[0] + "\n" : "";
            for ( var i = 1; i < stack.length; i++ )
            {
                msg += "    " + stack[i] + "\n";
            }
            msg += ex.stack;
        }
        else
        {
            msg = "An Error has occured in the TestFire Extension: \n\n" + msg;
        }

        alert(msg);
    };

    /**
     * Utility function: takes a string and a set of key:value pairs and appends the key:value pairs to the string.
     */
    this.addAttrs = function( str, attrs )
    {
        for ( var name in attrs )
        {
            var attr = this.escapeQuotes(attrs[name]);
            str += " " + name + '="' + attr + '"';
        }
        return str
    };

    this.xmlEncode = function ( value )
    {
        if ( !value )
            return value;
        else
            return String(value).replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
    }

    this.escapeQuotes = function ( str )
    {
        return this.xmlEncode(str);
    };

    this.valueIsOfType = function( value, type )
    {

        var dataType = typeof(value);
        if ( (type & this.TYPE_BOOLEAN) == this.TYPE_BOOLEAN )
        {
            if ( value === true || value === false )
            {
                return true;
            }
        }
        if ( (type & this.TYPE_INTEGER) == this.TYPE_INTEGER )
        {
            if ( dataType == 'number' && value.toString().indexOf('.') < 0 )
            {
                return true;
            }
        }
        if ( (type & this.TYPE_FLOAT) == this.TYPE_FLOAT )
        {
            if ( dataType == 'number' && value.toString().indexOf('.') >= 0 )
            {
                return true;
            }
        }
        if ( (type & this.TYPE_STRING) == this.TYPE_STRING )
        {
            if ( dataType == 'string' )
            {
                return true;
            }
        }
    };

    this.createMenu = function( popup, label )
    {
        var menu = popup.ownerDocument.createElement("menu");
        menu.setAttribute("label", label);
        return menu;
    };

    this.createMenuPopup = function( popup )
    {
        return popup.ownerDocument.createElement("menupopup");
    };

    /**
     * Clear and repopulate a menu with child menu items.
     * @todo move into utilities.
     */
    this.populateMenu = function( caller, menuItems )
    {
        FBL.eraseNode(caller);
        for ( var i = 0; i < menuItems.length; i++ )
        {
            UTIL.createMenuItem(caller, menuItems[i]);
        }
    };

    /**
     * Creates a menuItem and adds it to the passed popup.
     * @param popup The popup to add the created menu item to.
     * @param item The item to create, in the form:
     *  {
     *      label:
     *      type:
     *      [checked: bool]
     *      [disabled: bool]
     *      [ image: class: ]
     *  }
     *
     * OR
     *  {
     *      menuItems: (array of the above object),
     *      label:
     *  }
     */
    this.createMenuItem = function( popup, item )
    {
        if ( typeof(item) == "string" && item.indexOf("-") == 0 )
        {
            return FBL.createMenuSeparator(popup);
        }
        if ( "menuItems" in item )
        {
            var items = item.menuItems;
            var menuPopup = this.createMenuPopup(popup);
            var menu = this.createMenu(popup, item.label);
            for ( var i = 0; i < items.length; ++i )
            {
                this.createMenuItem(menuPopup, items[i]);
            }
            menu.appendChild(menuPopup);
            popup.appendChild(menu);
            return menuPopup;
        }

        var menuitem = popup.ownerDocument.createElement("menuitem");

        var label = item.nol10n ? item.label : this.$STR(item.label);

        menuitem.setAttribute("label", label);
        menuitem.setAttribute("type", item.type);
        if ( item.checked )
            menuitem.setAttribute("checked", "true");
        if ( item.disabled )
            menuitem.setAttribute("disabled", "true");
        if ( item.image )
        {
            menuitem.setAttribute("class", "menuitem-iconic");
            menuitem.setAttribute("image", item.image);
        }

        if ( item.command )
            menuitem.addEventListener("command", item.command, false);

        popup.appendChild(menuitem);
        return menuitem;
    };

    this.bindFixed = function()
    {
        try
        {
            var args = FBL.cloneArray(arguments), fn = args.shift(), object = args.shift();
            return function()
            {
                try
                { return fn.apply(object, FBL.extendArray(args, arguments));}
                catch( ex )
                {tf_debug(ex); }
            }
        }
        catch( ex )
        { tf_debug(ex); }

    };

    this.escapeHTML = function( str )
    {
        return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    };
    this.unescapeHTML = function( str )
    {
        return str.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
    };

    this.readFile = function( str_Filename, localizeFile )
    {
        if ( localizeFile )
        {
            str_Filename = this.getPrefsBasedFileName(str_Filename);
        }
        var obj_File = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
        obj_File.initWithPath(str_Filename);

        if ( !obj_File.exists() )
        {
            throw "File '" + str_Filename + "' does not exist.";
        }

        var obj_InputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
        obj_InputStream.init(obj_File, 0x01, 0444, null);

        var obj_ScriptableIO = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
        obj_ScriptableIO.init(obj_InputStream);

        var str = obj_ScriptableIO.read(obj_File.fileSize);

        obj_ScriptableIO.close();
        obj_InputStream.close();
        return str;
    };

    this.writeFile = function( str_Buffer, str_Filename, append, localizeFile )
    {
        if ( localizeFile )
        {
            str_Filename = this.getPrefsBasedFileName(str_Filename);
        }
        var F_READ_WRITE = 0x04;
        var F_CREATE_FILE = 0x08;
        var F_APPEND = 0x10;
        var F_TRUNCATE = 0x20;
        var flags = F_READ_WRITE | F_CREATE_FILE | F_TRUNCATE;
        if ( append )
        {
            flags = flags | F_APPEND;
        }
        try
        {
            var obj_File = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
            obj_File.initWithPath(str_Filename);
            if ( !obj_File.exists() )
                obj_File.create(0x00, 0644);
        }
        catch ( e )
        { tf_debug(e); }
        try
        {
            var obj_Transport = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
            obj_Transport.init(obj_File, flags, 064, 0);
            obj_Transport.write(str_Buffer, str_Buffer.length);
            obj_Transport.close();
        }
        catch ( e )
        {
            alert(e);
        }
    };

    this.getPrefsBasedFileName = function( saveFileName )
    {

        try
        {
            try
            {
                netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
            }
            catch ( e )
            {
                tf_debug(e);
            }
    // get the path to the user's home (profile) directory
            const DIR_SERVICE = new Components.Constructor("@mozilla.org/file/directory_service;1", "nsIProperties");
            try
            {
                var path = (new DIR_SERVICE()).get("ProfD", Components.interfaces.nsIFile).path;
            }
            catch ( e )
            {
                tf_debug(e);
            }
    // determine the file-separator
            if ( path.search(/\\/) != -1 )
            {
                path = path + "\\";
            }
            else
            {
                path = path + "/";
            }
            return path + saveFileName;

        }
        catch( ex )
        {
            tf_debug(ex);
        }
    };

    /**
     * Submit doesn't seem to work, though my server code could have been at fault.
     * Switched to an XMLHTTPReqeust.
     * @param formName
     * @param action
     * @param method
     * @param object
     */
    this.objectToForm = function( formName, action, method, object )
    {
        var doc = document;
        /*.implementation.createDocument("", "", null);*/

        var form = document.createElementNS("http://www.w3.org/1999/xhtml", "html:form")
    //        doc.body = doc.createElement("body");
        form.setAttribute("name", formName);
        form.setAttribute("method", method);
        form.setAttribute("action", action);
        for ( var objName in object )
        {
            form.appendChild(this.getInputField(objName, object[objName]));
        }
    //        doc.body.appendChild(form);
        return form;
    }

    this.getInputField = function( id, object )
    {
        var input = document.createElement("input");
        var type = object.type;
        var name = object.name ? "name='" + object.name + "' " : '';
        var value = "value='" + this.escapeHTML(object.value) + "'";
        input.setAttribute("id", id);
        input.setAttribute("type", type);
        input.setAttribute("name", name);
        input.setAttribute("type", value);
        return input;
    }

    this.toQueryString = function( hash )
    {
        var parts = [];
        var exclude = new Array();
        try
        {
            for ( var name in hash )
            {
                if ( name in exclude )
                {
                    continue;
                }
                parts.push(name + "=" + encodeURIComponent(hash[name]));
            }
        }
        catch( ex )
        {
            alert("toQueryString: Bad object. Cannot access a field of the passed hash object.");
        }
        return parts.join('&');
    };

    /**
     *
     * @param url
     * @param options
     */
    this.request = function( url, options )
    {
        var opts = {
            asynchronous: true,
            contentType: 'application/x-www-form-urlencoded',
            encoding: 'UTF-8',
            method: 'post',
            parameters: {},
            postBody: null,
            onComplete: function()
            {},
            onException: function()
            {},
            onFailure: function()
            {},
            onSuccess: function()
            {}
        }
        opts = this.extend(opts, options);
        var params = this.toQueryString(opts.parameters);
        if ( params )
        {
            if ( opts.method == 'get' )
            {
                url += "?" + params
            }
        }
        var body = null;
        if ( opts.method == 'post' )
        {
            if ( opts.postBody !== null )
            {
                body = opts.postBody;
            }
            else
            {
                body = params;
            }
        }

        try
        {
            var httpRequest = new XMLHttpRequest();
            httpRequest.overrideMimeType('text/xml');
            httpRequest.onreadystatechange = function()
            {
                if ( httpRequest.readyState == 4 )
                {
                    opts.onComplete.apply(this, [httpRequest]);
                    try
                    {
                        if ( httpRequest.status )
                        {
                            var noConnectionError = true;
                        }
                    }
                    catch( ex )
                    {
                        /*The most amazingly stupid, long lived bug ever. */
                        opts.onException.apply(this, [httpRequest, ex]);
                        return;
                    }

                    try
                    {
                        if ( httpRequest.status == 200 )
                        {
                            opts.onSuccess.apply(this, [httpRequest]);
                        }
                        else
                        {
                            if(opts.onFailure)
                                opts.onFailure.apply(this, [httpRequest]);
                        }
                    }
                    catch( ex )
                    {
                        Firebug.TestFire.util.tf_debug(ex);
                    }

                }
            }

            httpRequest.open(opts.method, url, opts.asynchronous);
            httpRequest.setRequestHeader("Content-Type", opts.contentType);
            httpRequest.send(body);
        }
        catch( ex )
        {
            opts.onException.apply(this, [httpRequest, ex]);
        }
        return true;
    };

    /**
     *  Form methods; slightly modified from Prototype.js
     *  http://www.prototypejs.org/assets/2007/6/20/prototype.js
     * Prototype JavaScript framework, version 1.5.1.1
     *  (c) 2005-2007 Sam Stephenson
     *
     *  Prototype is freely distributable under the terms of an MIT-style license.
     *  For details, see the Prototype web site: http://www.prototypejs.org/
     */

    this.extend = function( destination, source )
    {
        for ( var property in source )
        {
            destination[property] = source[property];
        }
        return destination;
    };

    /**
     * See Prototypejs.org api docs for 5.1.
     * Note that the options.parameters argument must be Hash (like) object, and cannot be a string
     * (unlike prototype.js)
     * @param form
     * @param options
     */
    this.formRequest = function( form, options, action )
    {
        options = options ? options : {};

        var params = options.parameters;
        options.parameters = this.serializeForm(form, true);

        if ( params )
        { // directly passed params take precedence over those parsed from the form.
            this.extend(options.parameters, params);
        }

        if ( form.hasAttribute('method') && !options.method )
        {
            options.method = form.method;
        }

        return this.request(action ? action : form.getAttribute('action'), options);
    };

    this.serializeForm = function( form, getHash )
    {
        return this.serializeFormElements(this.getFormElements(form), getHash);
    };

    this.getFormElements = function( form )
    {
        var els = form.elements;
        var elements = [];
        for ( var i = 0, L = els.length; i < L; i++ )
        {
            var child = els[i];
            if ( this.FormSerializers[child.tagName.toLowerCase()] )
            {
                elements.push(child);
            }
        }
        return elements;

    };
    this.serializeFormElements = function( elements, getHash )
    {
        var result = [];
        for ( var i = 0, L = elements.length; i < L; i++ )
        {
            var key = elements[i].name
            var value = this.getFormElementValue(elements[i]);
            if ( value != null )
            {
                if ( key in result ) // if the key (name of el) is already in the result list; as in a checkbox group.
                {
                    if ( result[key].constructor != Array )
                    {
                        result[key] = [result[key]];
                    }
                    result[key].push(value);
                }
                else
                {
                    result[key] = value;
                }
            }
        }
        return result;
    };

    this.getFormElementValue = function( element )
    {
        var method = element.tagName.toLowerCase();
        return this.FormSerializers[method](element);

    };
    /**
     * A nearly straight copy of Sam Stephenson's Serializers (prototype.js). Thanks Sam!
     */
    this.FormSerializers = {
        input: function( element )
        {
            switch ( element.type.toLowerCase() )
                {
                case 'checkbox':
                case 'radio':
                    return Firebug.TestFire.util.FormElementSerializers.inputSelector(element);
                default:
                    return Firebug.TestFire.util.FormElementSerializers.textarea(element);
            }
        },

        inputSelector: function( element )
        {
            return element.checked ? element.value : null;
        },

        textarea: function( element )
        {
            return element.value;
        },

        select: function( element )
        {
            return this[element.type == 'select-one' ?
                        'selectOne' : 'selectMany'](element);
        },

        selectOne: function( element )
        {
            var index = element.selectedIndex;
            return index >= 0 ? this.optionValue(element.options[index]) : null;
        },

        selectMany: function( element )
        {
            var values = [], length = element.length;
            if ( !length ) return null;

            for ( var i = 0; i < length; i++ )
            {
                var opt = element.options[i];
                if ( opt.selected ) values.push(this.optionValue(opt));
            }
            return values;
        },

        optionValue: function( opt )
        {
            return opt.hasAttribute('value') ? opt.value : opt.text;
        }
    };

    this.FormElementSerializers = {
        input: function( element )
        {
            switch ( element.type.toLowerCase() )
                {
                case 'checkbox':
                case 'radio':
                    return Firebug.TestFire.util.FormElementSerializers.inputSelector(element);
                default:
                    return Firebug.TestFire.util.FormElementSerializers.textarea(element);
            }
        },

        inputSelector: function( element )
        {
            return element.checked ? element.value : null;
        },

        textarea: function( element )
        {
            return element.value;
        },

        select: function( element )
        {
            return this[element.type == 'select-one' ?
                        'selectOne' : 'selectMany'](element);
        },

        selectOne: function( element )
        {
            var index = element.selectedIndex;
            return index >= 0 ? this.optionValue(element.options[index]) : null;
        },

        selectMany: function( element )
        {
            var values = [], length = element.length;
            if ( !length ) return null;

            for ( var i = 0; i < length; i++ )
            {
                var opt = element.options[i];
                if ( opt.selected ) values.push(this.optionValue(opt));
            }
            return values;
        },

        optionValue: function( opt )
        {
            // extend element because hasAttribute may not be native
            return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
        }
    };

}).apply(UTIL);



